/*
协程类的封装 - 代码参考 - [C++高级教程]从零开始开发服务器框架(sylar) https://github.com/sylar-yin/sylar

文件底部有对Linux系统协程操作函数的详细说明
*/

#ifndef __FIBER_H__
#define __FIBER_H__

#include <cstdint>
#include <memory>
#include <functional>
#include <sys/ucontext.h>
#include <ucontext.h>
#include <thread>


//每个线程都要创建一个主协程
//主协程负责去 创建、调度 子协程
//子协程执行完成后要返回主协程 - 或者让出执行时间回到主协程 - 子协程不能创建其他协程


//协程类
class Fiber: public std::enable_shared_from_this<Fiber>
{
    friend class Scheduler;
public:
    using ptr = std::shared_ptr<Fiber>;

    //协程状态
    enum State
    {
        INIT,   // 初始化
        HOLD,   // 暂停
        EXEC,   // 执行中
        TERM,   // 结束
        READY,  // 可执行
        EXCEPT  // 异常
    };

    //协程执行的函数
    //协程栈大小
    //是否在协程调度器上调度
    Fiber(std::function<void()> cb, size_t stacksize = 0, bool is_schedul = true);
    ~Fiber();

    //重置协程执行函数，并设置状态
    //getState() 为 INIT, TERM, EXCEPT 
    void resetFunc(std::function<void()> cb);

    //将当前协程切换到运行状态 （协程调度器使用）
    //getState() != EXEC  ->  getState() = EXEC
    void swapIn();

    //将当前协程切换到后台 （协程调度器使用）
    void swapOut();

    //将当前线程切换到执行状态 （线程单独使用）
    void call();

    //将当前线程切换到后台, 返回到线程的主协程 （线程单独使用）
    void back();

    //返回协程id
    uint64_t getId() const { return m_id;}

    //返回协程状态
    State getState() const { return m_state;}

public:
    //设置当前线程的运行协程
    static void SetThis(Fiber* f);

    //返回当前所在协程 - 如果当前线程还没有协程，就创建线程的主协程
    static Fiber::ptr GetThis();

    //将当前协程切换到后台,并设置为READY状态 （协程调度器使用）
    static void YieldToReady();

    //将当前协程切换到后台,并设置为HOLD状态 （协程调度器使用）
    static void YieldToHold();

    static void BackToReady();
    static void BackToHold();

    //返回当前协程的总数量
    static uint64_t TotalFibers();

    //协程执行函数  执行完成返回到调度协程
    static void SchedulMainFunc();

    //协程执行函数  执行完成返回到线程主协程
    static void MainFunc();

    //获取当前协程的id
    static uint64_t GetFiberId();

private:
    //默认构造函数用于构造线程的主协程
    Fiber();

    uint64_t m_id = 0;           //协程id
    uint32_t m_stackSize = 0;    //协程运行栈大小             
    State m_state = INIT;        //协程状态         
    ucontext_t m_ctx;            //协程上下文     
    void* m_stack = nullptr;     //协程运行栈指针             
    std::function<void()> m_cb;  //协程运行函数             
};


/*
getcontext()是一个C++函数，用于获取当前 线程/协程 的上下文信息
int getcontext(ucontext_t* ucp);

ucontext_t：线程/协程 上下文
typedef struct ucontext {
    struct ucontext *uc_link;  // 用于实现函数调用的返回地址：  指向上一个上下文的指针，通常为NULL。当当前上下文返回时，程序会跳转到uc_link指向的上下文中继续执行。
    sigset_t uc_sigmask;       // 当前线程的信号掩码：  用于控制哪些信号可以被接收。信号掩码的每个位表示一个信号，如果对应位为1，则表示该信号被屏蔽，不会被接收到。如果对应位为0，则表示该信号可以被接收到。
    stack_t uc_stack;          // 当前线程的栈信息：  包括栈的起始地址、大小等。当程序执行时，会使用栈来存储局部变量、函数参数、返回地址等信息。
    mcontext_t uc_mcontext;    // CPU寄存器的值：  包括通用寄存器、浮点寄存器等。当程序需要恢复执行时，需要将这些寄存器的值还原到之前保存的状态。
} ucontext_t;


其中的第三个参数 uc_stack
typedef struct stack {
    void  *ss_sp;   // 栈的起始地址，指向一块内存区域，用于存储栈中的数据。在使用makecontext()函数创建协程时，需要将该指针指向分配好的栈内存区域的起始地址。
    size_t ss_size; // 表示栈能够存储的数据大小（字节数）。在使用makecontext()函数创建协程时，需要将该值设置为栈的大小
    int    ss_flags;// 栈的标志
} stack_t;
需要注意的是，栈空间的大小应该足够容纳协程中的所有数据，否则可能会发生栈溢出等错误。

int    ss_flags;// 栈的标志：
SS_ONSTACK：当前线程或协程正在使用栈上的空间。
SS_DISABLE：禁用当前线程或协程的栈空间，使其使用系统分配的栈空间。
*/



/*
makecontext()是一个用于创建新协程的函数 用于设置协程的入口函数和栈空间等信息，它并不会立即执行新协程的入口函数
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
ucp：指向ucontext_t结构体的指针，表示新协程的上下文信息。需要在调用makecontext()函数之前，使用getcontext()函数获取当前线程或协程的上下文信息，并将其保存到ucp中。
func：一个无返回值、无参数的函数指针，表示新协程的入口函数。当新协程启动时，会自动调用该函数。
argc：表示可变参数的个数，其值应该为新协程入口函数的参数个数，如果入口函数不需要参数，该值应该为0。
...：可变参数列表，表示新协程入口函数的参数列表。如果入口函数不需要参数，该参数可以省略。
*/


/*
swapcontext() 用于保存当前执行上下文，并切换到指定的执行上下文
int swapcontext(ucontext_t *restrict oucp, const ucontext_t *restrict ucp);
oucp参数指向当前执行上下文的指针
ucp参数指向要切换到的执行上下文的指针
在函数调用后，当前执行上下文的状态会被保存到oucp指向的位置中，并切换到ucp指向的执行上下文中
*/

#endif